package com.ohos.chooseasy;

import com.ohos.chooseasy.utils.AttrUtils;

import ohos.agp.components.AttrSet;

import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.*;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.utils.net.Uri;

/**
 * 圆形图像视图
 *
 * @since 2021-05-25
 */
public class CircleImageView extends Image implements Component.DrawTask, Component.EstimateSizeListener {
    private static final Image.ScaleMode SCALE_TYPE = ScaleMode.CENTER;
    private static HiLogLabel hiLogLabel = new HiLogLabel(HiLog.LOG_APP, 0x22, "my_app");

    private static final int COLORDRAWABLE_DIMENSION = 2;

    private static final int DEFAULT_BORDER_WIDTH = 0;
    private static final int DEFAULT_BORDER_COLOR = Color.BLACK.getValue();
    private static final int DEFAULT_FILL_COLOR = Color.TRANSPARENT.getValue();
    private static final boolean DEFAULT_BORDER_OVERLAY = false;

    private final RectFloat mDrawableRect = new RectFloat();
    private final RectFloat mBorderRect = new RectFloat();

    private final Matrix mShaderMatrix = new Matrix();
    private final Paint mBitmapPaint = new Paint();
    private final Paint mBorderPaint = new Paint();
    private final Paint mFillPaint = new Paint();

    private int mBorderColor = DEFAULT_BORDER_COLOR;
    private int mBorderWidth = DEFAULT_BORDER_WIDTH;
    private int mFillColor = DEFAULT_FILL_COLOR;

    private PixelMap mBitmap;
    private PixelMapShader mBitmapShader;
    private int mBitmapWidth;
    private int mBitmapHeight;

    private float mDrawableRadius;
    private float mBorderRadius;

    private ColorFilter mColorFilter;

    private boolean mReady;
    private boolean mSetupPending;
    private boolean mBorderOverlay;
    private boolean mDisableCircularTransformation;
    private static final float DX_PROPORTION = 0.5f;
    private static final float LEFT_PROPORTION = 2f;
    private static final float TOP_PROPORTION = 2f;
    private static final float MBORDER_RADIUSP_ROPORTION = 2.0f;
    private static final float MDRAWABLE_RADIUS_PROPORTION = 2.0f;
    private static final float BORDERRECT = 1.0f;
    /**
     * 圆形图像视图
     *
     * @param context context
     */
    public CircleImageView(Context context) {
        super(context);
        init();
    }

    /**
     * 圆形图像视图
     *
     * @param context context
     * @param attrs   attrs
     */
    public CircleImageView(Context context, AttrSet attrs) {
        super(context, attrs);
        if (attrs != null) {
            if (attrs.getAttr("civ_fill_color").isPresent()) {
                mFillColor = AttrUtils.getColorValueByAttr(attrs, "civ_fill_color", new Color(DEFAULT_FILL_COLOR)).getValue();
            }
            if (attrs.getAttr("civ_border_width").isPresent()) {
                mBorderWidth = AttrUtils.getDimensionValueByAttr(attrs, "civ_border_width", DEFAULT_BORDER_WIDTH);
            }
            if (attrs.getAttr("civ_border_color").isPresent()) {
                mBorderColor = AttrUtils.getColorValueByAttr(attrs, "civ_border_color",
                        new Color(DEFAULT_BORDER_COLOR)).getValue();
            }
            if (attrs.getAttr("civ_border_overlay").isPresent()) {
                mBorderOverlay = AttrUtils.getBooleanValueByAttr(attrs, "civ_border_overlay", DEFAULT_BORDER_OVERLAY);
            }
        }
        init();
    }

    /**
     * 圆形图像视图
     *
     * @param context   context
     * @param attrs     attrs
     * @param styleName stylename
     */
    public CircleImageView(Context context, AttrSet attrs, String styleName) {
        super(context, attrs, styleName);
        init();
    }

    private void init() {
        setEstimateSizeListener(this);
        addDrawTask(this);
        super.setScaleMode(SCALE_TYPE);
        mReady = true;

        if (mSetupPending) {
            setup();
            mSetupPending = false;
        }
    }

    private void invalidateView() {
        invalidate();
        postLayout();
    }

    @Override
    public Image.ScaleMode getScaleMode() {
        return SCALE_TYPE;
    }

    @Override
    public void setScaleMode(Image.ScaleMode scaleType) {
        if (scaleType != SCALE_TYPE) {
            throw new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mDisableCircularTransformation) {
            addDrawTask(this);
            return;
        }
        drawCircleState(canvas);
    }

    private void drawCircleState(Canvas canvas) {
        mFillPaint.setStyle(Paint.Style.FILL_STYLE);
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(Color.GRAY);
        mFillPaint.setAlpha(0.85f);
        if (mFillColor != Color.TRANSPARENT.getValue()) {
            canvas.drawCircle(36.0f, 36.0f, 36.0f, mFillPaint);
        }
        canvas.drawCircle(mDrawableRect.getCenter().getPointX(), mDrawableRect.getCenter().getPointY(),
                mDrawableRadius, mBitmapPaint);
        if (mBorderWidth > 0) {
            canvas.drawCircle(mBorderRect.getCenter().getPointX(), mBorderRect.getCenter().getPointY(),
                    mBorderRadius, mBorderPaint);
        }
    }

    @Override
    public boolean onEstimateSize(int i, int i1) {
        setup();
        return false;
    }

    public int getBorderColor() {
        return mBorderColor;
    }

    /**
     * 设置边框颜色
     *
     * @param borderColor color
     */
    public void setBorderColor(int borderColor) {
        if (borderColor == mBorderColor) {
            return;
        }

        mBorderColor = borderColor;
        mBorderPaint.setColor(new Color(mBorderColor));
        invalidate();
    }

    /**
     * setBorderColorResource
     *
     * @param borderColorRes borderColorRes
     * @deprecated Use {@link #setBorderColor(int)} instead
     */
    @Deprecated
    public void setBorderColorResource(int borderColorRes) {
        setBorderColor(getContext().getColor(borderColorRes));
    }

    /**
     * Return the color drawn behind the circle-shaped drawable.
     *
     * @return The color drawn behind the drawable
     * @deprecated Fill color support is going to be removed in the future
     */
    @Deprecated
    public int getFillColor() {
        return mFillColor;
    }

    /**
     * Set a color to be drawn behind the circle-shaped drawable. Note that
     * this has no effect if the drawable is opaque or no drawable is set.
     *
     * @param fillColor The color to be drawn behind the drawable
     * @deprecated Fill color support is going to be removed in the future
     */
    @Deprecated
    public void setFillColor(int fillColor) {
        if (fillColor == mFillColor) {
            return;
        }

        mFillColor = fillColor;
        mFillPaint.setColor(new Color(fillColor));
        invalidate();
    }

    /**
     * Set a color to be drawn behind the circle-shaped drawable. Note that
     * this has no effect if the drawable is opaque or no drawable is set.
     *
     * @param fillColorRes fillColorRes
     */
    @Deprecated
    public void setFillColorResource(int fillColorRes) {
        setFillColor(getContext().getColor(fillColorRes));
    }

    public int getBorderWidth() {
        return mBorderWidth;
    }

    /**
     * 设置禁用循环变换
     *
     * @param borderWidth int
     */
    public void setBorderWidth(int borderWidth) {
        if (borderWidth == mBorderWidth) {
            return;
        }

        mBorderWidth = borderWidth;
        setup();
    }

    public boolean isBorderOverlay() {
        return mBorderOverlay;
    }

    /**
     * 设置禁用循环变换
     *
     * @param borderOverlay boolean
     */
    public void setBorderOverlay(boolean borderOverlay) {
        if (borderOverlay == mBorderOverlay) {
            return;
        }

        mBorderOverlay = borderOverlay;
        setup();
    }

    public boolean isDisableCircularTransformation() {
        return mDisableCircularTransformation;
    }

    /**
     * 设置禁用循环变换
     *
     * @param disableCircularTransformation boolean
     */
    public void setDisableCircularTransformation(boolean disableCircularTransformation) {
        if (mDisableCircularTransformation == disableCircularTransformation) {
            return;
        }

        mDisableCircularTransformation = disableCircularTransformation;
        initializeBitmap();
    }

    @Override
    public void setPixelMap(PixelMap bm) {
        super.setPixelMap(bm);
        initializeBitmap();
    }

    @Override
    public void setPixelMap(int resId) {
        super.setPixelMap(resId);
        initializeBitmap();
    }

    @Override
    public void setImageElement(Element drawable) {
        super.setImageElement(drawable);
        initializeBitmap();
    }

    /**
     * 设置图像URI
     *
     * @param uri uri
     */
    public void setImageuri(Uri uri) {
        initializeBitmap();
    }

    /**
     * 设置 颜色矩阵
     *
     * @param cf ColorFilter
     */
    public void setColorFilter(ColorFilter cf) {
        if (cf == mColorFilter) {
            return;
        }

        mColorFilter = cf;
        applyColorFilter();
        invalidate();
    }

    public ColorFilter getColorFilter() {
        return mColorFilter;
    }

    private void applyColorFilter() {
        if (mBitmapPaint != null) {
            mBitmapPaint.setColorFilter(mColorFilter);
        }
    }

    private PixelMap getBitmapFromDrawable(Element drawable) {
        if (drawable == null) {
            return null;
        }

        if (drawable instanceof PixelMapElement) {
            return ((PixelMapElement) drawable).getPixelMap();
        }

        try {
            PixelMap bitmap = null;

            if (drawable instanceof Element) {
                PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
                initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
                bitmap = PixelMap.create(new int[]{COLORDRAWABLE_DIMENSION,
                        COLORDRAWABLE_DIMENSION}, initializationOptions);
            } else {
                PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();
                initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
                bitmap = PixelMap.create(bitmap, new Rect(), initializationOptions);
            }
            PixelMapHolder pixelMapHolder = new PixelMapHolder(bitmap);
            Canvas canvas = new Canvas();
            RectFloat rectFloat = new RectFloat();
            Paint paint = new Paint();
            canvas.drawPixelMapHolderRect(pixelMapHolder, rectFloat, paint);

            drawable.setBounds(0, 0, canvas.getLocalClipBounds().getWidth(), canvas.getLocalClipBounds().getHeight());
            drawable.drawToCanvas(canvas);
            return bitmap;
        } catch (Exception e) {
            HiLog.error(hiLogLabel, "error" + e.getMessage());
            return null;
        }
    }

    private void initializeBitmap() {
        if (mDisableCircularTransformation) {
            mBitmap = null;
        } else {
            mBitmap = getBitmapFromDrawable(getBackgroundElement());
        }
        setup();
    }

    private void setup() {
        if (!mReady) {
            mSetupPending = true;
            return;
        }

        if (getWidth() == 0 && getHeight() == 0) {
            return;
        }

        if (mBitmap == null) {
            invalidate();
            return;
        }
        PixelMapHolder pixelMapHolder = new PixelMapHolder(mBitmap);
        mBitmapShader = new PixelMapShader(pixelMapHolder,
                Shader.TileMode.CLAMP_TILEMODE, Shader.TileMode.CLAMP_TILEMODE);
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setShader(mBitmapShader, null);
        mBorderPaint.setStyle(Paint.Style.STROKE_STYLE);
        mBorderPaint.setAntiAlias(true);
        mBorderPaint.setColor(new Color(mBorderColor));
        mBorderPaint.setStrokeWidth(mBorderWidth);
        mFillPaint.setStyle(Paint.Style.FILL_STYLE);
        mFillPaint.setAntiAlias(true);
        mFillPaint.setColor(new Color(mFillColor));
        mBitmapHeight = mBitmap.getImageInfo().size.height;
        mBitmapWidth = mBitmap.getImageInfo().size.width;

        mBorderRect.fuse(calculateBounds());
        mBorderRadius = Math.min((mBorderRect.getHeight() - mBorderWidth) / MBORDER_RADIUSP_ROPORTION,
                (mBorderRect.getWidth() - mBorderWidth) / MBORDER_RADIUSP_ROPORTION);

        mDrawableRect.fuse(mBorderRect);
        if (!mBorderOverlay && mBorderWidth > 0) {
            mDrawableRect.fuse(mBorderWidth - BORDERRECT,mBorderWidth - BORDERRECT);
        }
        mDrawableRadius = Math.min(mDrawableRect.getHeight() / MDRAWABLE_RADIUS_PROPORTION,
                mDrawableRect.getWidth() / MDRAWABLE_RADIUS_PROPORTION);

        applyColorFilter();
        updateShaderMatrix();
        invalidate();
    }

    private RectFloat calculateBounds() {
        int availableWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        int availableHeight = getHeight() - getPaddingTop() - getPaddingBottom();

        int sideLength = Math.min(availableWidth, availableHeight);

        float left = getPaddingLeft() + (availableWidth - sideLength) / LEFT_PROPORTION;
        float top = getPaddingTop() + (availableHeight - sideLength) / TOP_PROPORTION;

        return new RectFloat(left, top, left + sideLength, top + sideLength);
    }

    private void updateShaderMatrix() {
        float scale;
        float dx = 0;
        float dy = 0;
        mShaderMatrix.setMatrix(null);

        if (mBitmapWidth * mDrawableRect.getHeight() > mDrawableRect.getWidth() * mBitmapHeight) {
            scale = mDrawableRect.getHeight() / (float) mBitmapHeight;
            dx = (mDrawableRect.getWidth() - mBitmapWidth * scale) * DX_PROPORTION;
        } else {
            scale = mDrawableRect.getWidth() / (float) mBitmapWidth;
            dy = (mDrawableRect.getHeight() - mBitmapHeight * scale) * DX_PROPORTION;
        }

        mShaderMatrix.setScale(scale, scale);
        mShaderMatrix.postTranslate((int) (dx + DX_PROPORTION) + mDrawableRect.left,
                (int) (dy + DX_PROPORTION) + mDrawableRect.top);
        mBitmapShader.setShaderMatrix(mShaderMatrix);
    }
}